
/*******************************************************************************
 * Copyleft (c) 2021 Kcode
 *
 * @file    s3c2440_iis.c
 * @brief   ASOC框架下的platform部分，对s3c2440控制芯片的iis接口进行设置，
 *          参考sound\soc\samsung\s3c24xx-i2s.c
 *
 * @note    核心结构体snd_soc_dai_driver
 * @author  K
 * @version 0.0.1
 * @date    2021-08-07
 * @license MulanPSL-1.0
 *
 * 文件修改历史：
 * <时间>       | <版本>    | <作者>  | <描述>
 * 2021-08-07   | v0.0.1    | Kcode   | s3c2440控制芯片的iis接口的设置
 * -----------------------------------------------------------------------------
 ******************************************************************************/

#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/module.h>

#include <sound/soc.h>
#include <sound/pcm_params.h>

#include <mach/regs-gpio.h>
#include <mach/dma.h>

#define ABS(a, b) ((a>b)?(a-b):(b-a))

/*!
 * iis接口的相关寄存器
 */
typedef struct s3c2440_iis_regs {
    unsigned int iiscon;
    unsigned int iismod;
    unsigned int iispsr;
    unsigned int iisfcon;
    unsigned int iisfifo;
}S3C2440_IIS_REGS_S, *S3C2440_IIS_REGS_P;

static volatile unsigned int *gpecon;
static volatile S3C2440_IIS_REGS_P iis_regs;

/*!
 * @brief  iis传输启动
 * @return void
 */
static void s3c2440_iis_start(void)
{
    iis_regs->iiscon |= (1 << 0);
}

/*!
 * @brief  iis传输停止
 * @return void
 */
static void s3c2440_iis_stop(void)
{
    iis_regs->iiscon &= ~(1 << 0);
}

/*!
 * @brief 根据params参数设置s3c2440控制芯片上的iis接口寄存器设置
 * @return int 0：成功，其他：失败
 */
static int s3c2440_i2s_hw_params(struct snd_pcm_substream *substream,
				        struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
    /* 根据params参数设置s3c2440控制芯片上的iis接口 */

    int i;
    int pre;
    int min;
    unsigned int tmp_fs;
    unsigned int fs;
    struct clk *clk;
    
    /*!
     * 配置GPIO用于IIS 
     */
    *gpecon &= ~((3 << 0) | (3 << 2) | (3 << 4) | (3 << 6) | (3 << 8));
    *gpecon |= ((2 << 0) | (2 << 2) | (2 << 4) | (2 << 6) | (2 << 8));

    /*! 
     * iismod: IIS模式寄存器
     *  bit[9]   : Master clock select, 0-PCLK，默认
     *  bit[8]   : 0 = Master mode(主动模式)，默认
     *  bit[7:6] : 10 = Transmit mode(发送模式)
     *  bit[5]   : 声道极性，使用默认，不设置
     *  bit[4]   : 0-IIS compatible format (Left)-justified format(兼容codec芯片的格式)
     *  bit[3]   : 每位采样需要的bit数，0 = 8bit，1 = 16bit
     *  bit[2]   : 384fs, 确定了MASTER CLOCK之后, fs = MASTER CLOCK/384
     *  bit[1:0] : Serial bit clock frequency select, 32fs
     */
    if (params_format(params) == SNDRV_PCM_FORMAT_S8) {
        iis_regs->iismod = (2 << 6) | (0 << 4) | (0 << 3) | (1 << 2) | (1 << 0);
    } else if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) {
        iis_regs->iismod = (2 << 6) | (0 << 4) | (1 << 3) | (1 << 2) | (1 << 0);
    } else 
        return -EINVAL;

    /*!
     * IISPSR: IIS分频寄存器
     *  Master clock = PCLK/(n+1)
     *  fs = Master clock / 384
     *  fs = PCLK / (n+1) / 384
     */
    pre = 0;
    min = 0xffff;
    fs  = params_rate(params);
    clk = clk_get(NULL, "pclk");
    for (i = 0; i < 31; i++) {
        /* 计算临时采样率 */
        tmp_fs = clk_get_rate(clk) / 384 / (i + 1);

        /* 得到使采样率误差最小的分频数 */
        if (ABS(tmp_fs, fs) < min) {
             min = ABS(tmp_fs, fs);
             pre = i;
        }
    }
    iis_regs->iispsr = (pre << 5) | (pre << 0);

    /*! 
     * IISFCON: IIS FIFO接口寄存器
     *  bit[15] : Transmit FIFO access mode select, 1-DMA
     *  bit[13] : Transmit FIFO, 1-enable
     */
    iis_regs->iisfcon = (1 << 15) | (1 << 13);

    /*!
     * IISCON: IIS控制寄存器
     *  bit[5] : Transmit DMA service request, 1-enable
     *  bit[1] : IIS prescaler, 1-enable
     */
    iis_regs->iiscon = (1 << 5) | (1 << 1);

    clk_put(clk);
    
	return 0;
}

/*!
 * @brief iis传输的触发工作，根据cmd启动或停止iis传输
 * @return int 0：成功，-EINVAL：失败
 */
static int s3c2440_iis_trigger(struct snd_pcm_substream *substream, int cmd,
                   struct snd_soc_dai *dai)
{
    int ret = 0;
    
    switch (cmd) {
    case SNDRV_PCM_TRIGGER_START:
    case SNDRV_PCM_TRIGGER_RESUME:
    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
        s3c2440_iis_start();
        break;
    
    case SNDRV_PCM_TRIGGER_STOP:
    case SNDRV_PCM_TRIGGER_SUSPEND:
    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        s3c2440_iis_stop();
        break;
        
    default:
        ret = -EINVAL;
        break;
    }

    return ret;
}

/*!
 * 在控制芯片上iis的具体操作函数信息
 */
static const struct snd_soc_dai_ops s3c2440_i2s_dai_ops = {
	.hw_params	= s3c2440_i2s_hw_params,
    .trigger = s3c2440_iis_trigger,

};

#define S3C24XX_I2S_RATES \
	(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
	SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
	SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)

/*!
 * 在控制芯片上指定编解码芯片支持的属性
 */
static struct snd_soc_dai_driver s3c2440_iis_dai = {
	.playback = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = S3C24XX_I2S_RATES,
		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
	.capture = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = S3C24XX_I2S_RATES,
		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
	.ops = &s3c2440_i2s_dai_ops,
};

/*!
 * @brief 关闭设备的函数，必须得有，否则程序编译报错
 * @return void
 */
static void s3c2440_iis_dev_release(struct device *dev)
{}

/*!
 * 分配注册一个名为s3c2440_iis_dev的平台设备
 */
static struct platform_device s3c2440_iis_dev = {
    .name    = "s3c2440-iis",
    .id      = -1,
    .dev  = {
        .release = s3c2440_iis_dev_release,
    },
};

/*!
 * @brief  根据名字，连接到对应的平台驱动与设备时所调用的函数
 * @return int
 */
static int s3c2440_iis_probe(struct platform_device *pdev)
{    
    return snd_soc_register_dai(&pdev->dev, &s3c2440_iis_dai);
}

/*!
 * @brief 关闭平台驱动的函数，必须得有，否则程序编译报错
 * @return int
 */
static int s3c2440_iis_remove(struct platform_device *pdev)
{
    snd_soc_unregister_dai(&pdev->dev);
    return 0;
}

/*!
 * 分配注册一个名为s3c2440_iis_dev的平台驱动
 */
struct platform_driver s3c2440_iis_drv = {
    .probe   = s3c2440_iis_probe,
    .remove  = s3c2440_iis_remove,
    .driver  = {
        .name = "s3c2440-iis",
    },
};

/*!
 * @brief s3c2440的iis接口的寄存器映射
 * @return void
 */
static void s3c2440_iis_regs_map(void)
{
    gpecon    = ioremap(0x56000040, 4);
    iis_regs = ioremap(0x56000000, sizeof(S3C2440_IIS_REGS_S));
}

/*!
 * @brief s3c2440的iis接口的寄存器取消映射
 * @return void
 */
static void s3c2440_iis_regs_unmap(void)
{
    iounmap(gpecon);
    iounmap(iis_regs);
}

/*!
 * @brief 入口函数，注册平台设备与平台驱动，映射寄存器
 * @return int 0：注册成功，1：注册失败
 */
static int s3c2440_iis_init(void)
{
    int ret = 0;
    struct clk *clk;
    
    s3c2440_iis_regs_map();

    clk = clk_get(NULL, "pclk");
    clk_enable(clk);
    clk_put(clk);
    
    ret = platform_device_register(&s3c2440_iis_dev);
    if (ret) {
        printk("Can not register platform s3c2440_iis_dev!\n");
        goto fail;
    }

    ret = platform_driver_register(&s3c2440_iis_drv);
    if (ret) {
        printk("Can not register platform s3c2440_iis_drv!\n");
        platform_device_unregister(&s3c2440_iis_dev);
        goto fail;
    }

fail:    
    return ret;
}

/*!
 * @brief 出口函数
 * @return int，
 */
static void s3c2440_iis_exit(void)
{
    struct clk *clk;

    s3c2440_iis_regs_unmap();
    
    platform_driver_unregister(&s3c2440_iis_drv);
    platform_device_unregister(&s3c2440_iis_dev);

    clk = clk_get(NULL, "pclk");
    clk_disable(clk);
    clk_put(clk);

}
 
 /* 修饰 */
 module_init(s3c2440_iis_init);
 module_exit(s3c2440_iis_exit);
 
 /* 协议 */
 MODULE_LICENSE("GPL");


